/*
 * Copyright (c) United Binary LLC.  All rights reserved.
 * 
 * This code is licensed under the MIT License
 * 
 * SEE: http://harnessit.codeplex.com/license
 * 
 */
#region using ...
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
#endregion

namespace UnitedBinary.Core.IO
{
	/// <include file='IO.xml' path='/Docs/RelativePaths/Class/*'/>
	public class RelativePaths
	{
		private string basepath = string.Empty;

		/// <include file='IO.xml' path='/Docs/RelativePaths/ctor_string/*'/>
		public RelativePaths(string basePath)
		{
			if (basePath == null || basePath.Length == 0)
			{
				throw new ArgumentException( "Invalid base path." );
			}

			BasePath = basePath;
		}

		/// <include file='IO.xml' path='/Docs/RelativePaths/ctor/*'/>
		public RelativePaths()
		{
		}

		/// <include file='IO.xml' path='/Docs/RelativePaths/GetRelativePath/*'/>
		public string GetRelativePath(string fullPath)
		{
			if (fullPath == null)
			{
				throw new ArgumentNullException( "fullPath" );
			}

			bool isCommonPath = CheckForCommonDrive( fullPath );
			bool isCommonNetwork = CheckForCommonNetworkPath( fullPath );
			if (!isCommonPath && !isCommonNetwork)
			{
				return fullPath;
			}

			char[] delims = {'\\'};
			string[] baseArray = BasePath.Split( delims );
			string[] fullArray = fullPath.Split( delims );
			int divergenceIndex = 0;
			for (int i=0; i < System.Math.Min( baseArray.Length, fullArray.Length ); i++)
			{
				if (string.Compare(baseArray[i], fullArray[i], true) != 0)
				{
					divergenceIndex = i;
					break;
				}
			}
			if (divergenceIndex == 0)
			{
				divergenceIndex = baseArray.Length;
			}

			int upCount = baseArray.Length - divergenceIndex;
			string relPath = string.Empty;
			for (int i=0; i < upCount; i++)
			{
				relPath += "..\\";
			}
			for (int i=divergenceIndex; i < fullArray.Length; i++)
			{
				relPath += fullArray[i];
				if (i < fullArray.Length-1)
				{
					relPath += "\\";
				}
			}

			Debug.WriteLine( "Divergence at index " + divergenceIndex );
			Debug.WriteLine( "Full path: " + fullPath );
			Debug.WriteLine( "Base path: " + BasePath );
			Debug.WriteLine( "Generated relative path: " + relPath );

			return relPath;
		}

		private bool CheckForCommonNetworkPath(string fullPath)
		{
			if (fullPath.Length == 0)
			{
				return false;
			}

			string basePath = BasePath;
			bool isBaseNetwork = basePath.StartsWith( @"\\" );
			bool isFullNetwork = fullPath.StartsWith( @"\\" );
			bool bothAreNetworks = isBaseNetwork && isFullNetwork;

			if (!bothAreNetworks)
			{
				return false;
			}

			int nextSlash = basePath.IndexOf( '\\', 2 );
			if (nextSlash < 0)
			{
				return false;
			}

			string commonRoot = basePath.Substring( 0, nextSlash + 1 );

			bool commonNetwork =
				string.Compare( basePath, 0, fullPath, 0, nextSlash+1, true ) == 0;

			return commonNetwork;
		}

		private bool CheckForCommonDrive(string fullPath)
		{
			if (fullPath.Length == 0)
			{
				return false;
			}

			string basePath = BasePath;
			bool isBaseDrive = basePath[1] == ':';
			bool isFullDrive = fullPath[1] == ':';

			bool bothAreDrives = isBaseDrive && isFullDrive;
			bool commonDrives =
				bothAreDrives &&
				char.ToLower(basePath[0]) == char.ToLower( fullPath[0] );

			if (bothAreDrives)
			{
				return commonDrives;
			}
			return false;
		}

		/// <include file='IO.xml' path='/Docs/RelativePaths/BasePath/*'/>
		public string BasePath
		{
			get
			{
				return basepath;
			}
			set
			{
				if (value == null)
				{
					throw new ArgumentNullException( "value" );
				}

				string startingSlashes = GetPrefixedSlashes( value.Trim() );

				basepath = value.Trim( new char[]  {' ', '\\'} );
				basepath = startingSlashes + basepath;
			}
		}

		private string GetPrefixedSlashes(string value)
		{
			StringBuilder sb = new StringBuilder();
			for (int i=0; i < value.Length; i++)
			{
				if (value[i] != '\\')
				{
					break;
				}
				else
				{
					sb.Append( '\\' );
				}
			}

			string startingSlashes = sb.ToString();
			return startingSlashes;
		}

		/// <include file='IO.xml' path='/Docs/Empty/Empty/*'/>
		[EditorBrowsable(EditorBrowsableState.Never)]
		public string DirectoryFromFileName(string fullPathName)
		{
			if (fullPathName == null || fullPathName.Length == 0)
			{
				throw new ArgumentException( "Invalid FullPathName." );
			}

			int dirIndex = fullPathName.Length;
			for (int i=fullPathName.Length-1; i > 0; i--)
			{
				if (fullPathName[i] == '\\')
				{
					dirIndex = i;
					break;
				}
			}

			if (dirIndex == fullPathName.Length)
			{
				return fullPathName;
			}

			string dir = fullPathName.Remove( dirIndex, fullPathName.Length - dirIndex );
			char[] trimmer = {'\\',' '};
			dir = dir.Trim( trimmer );

			Debug.WriteLine( "DirectoryFromFileName: Converted-> " + fullPathName );
			Debug.WriteLine( "DirectoryFromFileName: To--------> " + dir );

			return dir;
		}

		/// <include file='IO.xml' path='/Docs/RelativePaths/RebuildFullPath/*'/>
		public string RebuildFullPath(string relativeFilePath)
		{
			if (relativeFilePath == null || relativeFilePath.Length < 2)
			{
				return this.BasePath;
			}

			//
			// Check some special cases that don't fit the algorithm.
			// 1. The file is on a different drive than base (nothing relative to work with).
			// 2. The base path is a relative path itself.
			//
			if (relativeFilePath[1] == ':')
			{
				Debug.WriteLine( "Relative path contains drive letter - it is already a full path.  Returning." );
				return relativeFilePath;
			}
			else if (relativeFilePath.StartsWith(@"\\"))
			{
				Debug.WriteLine( "Relative path is a newtwork share - it is already a full path.  Returning." );
				return relativeFilePath;
			}

			if (BasePath.StartsWith(".."))
			{
				Debug.WriteLine( "Base is relative path, concatenating." );
				return BasePath + "\\" + relativeFilePath;
			}

			char[] splitter = {'\\'};
			string[] dirs = relativeFilePath.Split( splitter );
			int upCount = 0;
			foreach (string dir in dirs)
			{
				if (dir == "..")
				{
					upCount++;
				}
			}

			Debug.WriteLine( "Up count = " + upCount );

			StringBuilder sbFullPath2 = new StringBuilder();
			for (int i=upCount; i < dirs.Length; i++)
			{
				sbFullPath2.Append("\\").Append( dirs[i] );
			}
			string fullPath2 = sbFullPath2.ToString();

			Debug.WriteLine( "FullPath2 = " + fullPath2 );

			dirs = BasePath.Split( splitter );

			StringBuilder sbFullPath1 = new StringBuilder();
			sbFullPath1.Append( dirs[0] );
			for (int i=1; i < dirs.Length-upCount; i++)
			{
				sbFullPath1.Append("\\").Append( dirs[i] );
			}
			string fullPath1 = sbFullPath1.ToString();

			Debug.WriteLine( "FullPath1 = " + fullPath1 );

			string fullPath = fullPath1 + fullPath2;
			Debug.WriteLine( "FullPath = " + fullPath );

			return fullPath;
		}
	}
}
